﻿#include "../include/QtCEFWidget.h"

#include <QApplication>
#include <QDir>

#include "QtCEFApp.h"
#include "QtCEFHandler.h"

namespace {
// user agent
#define QTCEF_USER_AGENT "QtCef/1.0 (Windows; zh-CN)"

// 本地缓存数据目录
#define CACHE_DIR ".cache"

// 默认语言
#define DEFAULT_LOCAL "zh-CN"

// 生成 CEF 运行配置
CefSettings& make_settings(CefRefPtr<CefCommandLine>& command_line) {
#ifndef NDEBUG
    QDir app_dir = QString(qApp->applicationDirPath());
#else
    QDir app_dir                     = QDir::current();
#endif

    static CefSettings s_settings;

    // render 进程名
    // QString str_exe_path = exe_dir.filePath(RENDER_PROCESS_NAME);
    // CefString(&s_settings.browser_subprocess_path) =
    //    QDir::toNativeSeparators(str_exe_path).toStdString();

    // 资源文件目录
    // QString str_res_path = exe_dir.filePath(RES_DIR);
    // CefString(&s_settings.resources_dir_path) =
    // QDir::toNativeSeparators(str_res_path).toStdString();

    // 国际化资源文件目录
    // QDir ResPath(str_res_path);
    // CefString(&s_settings.locales_dir_path) =
    // QDir::toNativeSeparators(ResPath.filePath(LOCALES_DIR)).toStdString();

    CefString(&s_settings.user_agent) = QTCEF_USER_AGENT;

    // Store cache data will on disk.
    QString str_cache_path            = app_dir.filePath(CACHE_DIR);
    CefString(&s_settings.cache_path) = QDir::toNativeSeparators(str_cache_path).toStdString();

    // CefString(&s_settings.user_data_path) = ;
    // CefString(&s_settings.accept_language_list) = ;
    // cef_settings_.persist_user_preferences = CCefSetting::persist_user_preferences;
    // cef_settings_.background_color = CCefSetting::background_color;

    // 设置默认语言
    CefString(&s_settings.locale) = CefString(DEFAULT_LOCAL);

    // 不使用沙盒
    s_settings.no_sandbox = true;

    s_settings.pack_loading_disabled = false;

    // 本地保存cookie
    s_settings.persist_session_cookies = 1;

    // Make browser process message loop run in a separate thread.
    s_settings.multi_threaded_message_loop = true;

    if (command_line->HasSwitch("enable-chrome-runtime")) {
        // Enable experimental Chrome runtime. See issue #2969 for details.
        s_settings.chrome_runtime = true;
    }

#ifndef NDEBUG
    s_settings.log_severity          = LOGSEVERITY_DEFAULT;
    s_settings.remote_debugging_port = 9000;
#else
    s_settings.log_severity          = LOGSEVERITY_DISABLE;
    s_settings.remote_debugging_port = 9000;
#endif

    return s_settings;
}
}   // namespace

//////////////////////////////////////////////////////////////////////////
// QtCEFWidget::impl
class QtCEFWidget::impl {
public:
    explicit impl(HWND win_id, const QRect& rect, const std::string& default_url);
    virtual ~impl();

    // 初始化CEF
    static int init_cef();
    // CEF退出
    static void exit_cef();

    // 打开指定的URL
    void to_url(const std::string& url);

    // 移动到指定位置（包括位置和大小）
    void resize_to(const QSize& size) {
        if (!m_browser_handler)
            return;

        m_browser_handler->resize_to(0, 0, size.width(), size.height());
    }

    // 关闭所有的浏览窗口（只发关闭消息，并不一定完成关闭）
    void do_close();
    // 检查是否可以退出（CEF完成退出操作）
    bool can_quit();

private:
    CefRefPtr<QtCEFHandler> m_browser_handler;
};

int QtCEFWidget::impl::init_cef() {
    static bool cef_initialized(false);

    // 避免重复初始化
    if (cef_initialized)
        return -1;

    // CefEnableHighDPISupport();

    const HINSTANCE h_instance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));

    CefMainArgs main_args(h_instance);
    const int   exit_code = CefExecuteProcess(main_args, nullptr, nullptr);
    if (exit_code >= 0) {
        return exit_code;
    }

    CefRefPtr<QtCEFApp> app(new QtCEFApp);   // CefApp实现，用于处理进程相关的回调。

    // Parse command-line arguments for use in this method.
    CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
    command_line->InitFromString(::GetCommandLineW());

    // CEF init
    cef_initialized = CefInitialize(main_args, make_settings(command_line), app.get(), nullptr);

    return -1;
}

void QtCEFWidget::impl::exit_cef() {
    // 关闭CEF，释放资源
    CefShutdown();
}

QtCEFWidget::impl::impl(HWND win_id, const QRect& rect, const std::string& default_url) {
    CefWindowInfo cef_wnd_info;
    std::string   str_url = default_url;
    CefRect       win_rect(rect.left(), rect.top(), rect.width(), rect.height());

    // 将cef界面嵌入qt界面中
    cef_wnd_info.SetAsChild(win_id, win_rect);
    CefBrowserSettings cef_browser_settings;
    m_browser_handler = CefRefPtr<QtCEFHandler>(new QtCEFHandler);
    bool ret          = CefBrowserHost::CreateBrowser(cef_wnd_info,
                                             m_browser_handler,
                                             str_url,
                                             cef_browser_settings,
                                             nullptr,
                                             CefRequestContext::GetGlobalContext());
    if (!ret)
        LOG(ERROR) << "create browser error.";
}

QtCEFWidget::impl::~impl() {
    m_browser_handler.release();
}

void QtCEFWidget::impl::to_url(const std::string& url) {
    // 这里不用检查 m_browser_handler，因为在构造函数里已经 new 了
    m_browser_handler->to_url(url);
}

void QtCEFWidget::impl::do_close() {
    if (m_browser_handler && !m_browser_handler->is_closing())
        m_browser_handler->close_all_browsers(true);
}

bool QtCEFWidget::impl::can_quit() {
    return m_browser_handler->can_quit();
}

//////////////////////////////////////////////////////////////////////////
// QtCEFWidget
std::list<QtCEFWidget*> QtCEFWidget::m_widgets;

QtCEFWidget::QtCEFWidget(QWidget* parent, const QString& default_url)
    : QWidget(parent) {
    m_impl =
        new QtCEFWidget::impl((HWND)this->winId(), this->geometry(), default_url.toStdString());

    m_widgets.push_back(this);
}

QtCEFWidget::~QtCEFWidget() {
    m_widgets.remove(this);

    delete m_impl;
    m_impl = nullptr;
}

int QtCEFWidget::init_cef() {
    return QtCEFWidget::impl::init_cef();
}

void QtCEFWidget::exit_cef() {
    // 通知各个 CEF 控件关闭
    for (auto widget : m_widgets)
        widget->do_close();

    // 等待所有 CEF 控件关闭过程结束
    for (auto widget : m_widgets)
        while (!widget->can_quit()) {
            Sleep(200);
            qApp->processEvents();
        }

    return QtCEFWidget::impl::exit_cef();
}

#include <QDesktopWidget>

void QtCEFWidget::resizeEvent(QResizeEvent* /*e*/) {
    const QRect& r = this->geometry();

    // int dpi_x = physicalDpiX();
    // int dpi_y = physicalDpiY();
    //// values 284 and 285 are the reference values
    // double scale_x = dpi_x <= 110 ? 1 : 284.0 / double(dpi_x);
    // double scale_y = dpi_y <= 110 ? 1 : 285.0 / double(dpi_y);

    // return m_impl->resize_to(QSize(r.width() * scale_x, r.height() * scale_y));

    return m_impl->resize_to(QSize(r.width(), r.height()));
}

void QtCEFWidget::to_url(const std::string& url) {
    return m_impl->to_url(url);
}

void QtCEFWidget::do_close() {
    return m_impl->do_close();
}

bool QtCEFWidget::can_quit() {
    return m_impl->can_quit();
}